Skip to content

feat: emit and parse durations as ISO-8601 in launch form converter#7596

Open
ursucarina wants to merge 1 commit into
v2from
carina/duration
Open

feat: emit and parse durations as ISO-8601 in launch form converter#7596
ursucarina wants to merge 1 commit into
v2from
carina/duration

Conversation

@ursucarina

@ursucarina ursucarina commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Why are the changes needed?

Launch form duration fields are declared in JSON Schema with format: "duration", which per the spec means an ISO-8601 duration string (e.g. P2DT3H). The converter instead emitted Go-style strings (time.Duration.String(), e.g. 2h30m) and only parsed bare-seconds or Go-style strings on the way back in. This made the converter's DURATION handling inconsistent with the format: "duration" schema it produces, and it rejected ISO-8601 input.

What changes were proposed in this pull request?

This makes the launch form converter (runs/service/converter/literal_json_converter.go) speak ISO-8601 for DURATION:

  • literalToJsonValues now formats durations as ISO-8601 (formatISO8601Duration) instead of Duration.String().
  • jsonValueToLiteral (the SimpleType_DURATION case) now parses ISO-8601 first, then falls back to bare seconds and Go-style duration strings for backward compatibility.
  • Added isISO8601Duration / parseISO8601Duration / formatISO8601Duration helpers. Year and month lengths are fixed (365-day years, 30-day months) so durations round-trip deterministically.

How was this patch tested?

Unit tests in runs/service/converter/literal_json_converter_test.go:

  • Updated the existing duration literal assertion to expect ISO-8601 (PT1H30M).
  • Added TestFormatISO8601Duration, TestParseISO8601Duration (valid, invalid, and format↔parse round-trip cases), and TestJSONValuesToLiteralsDuration covering ISO-8601 plus the backward-compatible bare-seconds and Go-style inputs.

go test ./runs/service/converter/ passes.

Labels

  • changed

Check all the applicable boxes

  • I updated the documentation accordingly.
  • All new and existing tests passed.
  • All commits are signed-off.

Signed-off-by: Carina Ursu <carina@union.ai>

@EngHabu EngHabu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error I get even when I write 2h. Which is a valid time.ParseDuration value I thought. So I'm a bit confused

// formatISO8601Duration converts a time.Duration into an ISO-8601 duration string
// (e.g. "P2DT3H4M5S"). Only days/hours/minutes/seconds are emitted (never years/months) so the
// representation is unambiguous; the launch form frontend renders it back to a readable string.
func formatISO8601Duration(d time.Duration) string {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use a library for this

https://github.com/dustin/go-humanize


// parseISO8601Duration parses an ISO-8601 duration string (e.g. "P2DT3H4M5S") into a
// durationpb.Duration. Returns an error if the string is not a valid ISO-8601 duration.
func parseISO8601Duration(s string) (*durationpb.Duration, error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we usea library instead?

channelmeter/iso8601duration

Maybe...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From me looking:

Short answer: we could, but channelmeter/iso8601duration is a poor fit and would actually be a regression. It's not currently a dependency either, so we'd be adding a new (unmaintained) module.

The two dealbreakers:

  • It doesn't cover the formatting direction. String() only formats the library's own Duration struct (Years/Weeks/Days/Hours/Minutes/Seconds), not a Go time.Duration. Our literalToJsonValues path needs time.Duration → ISO string, so we'd still hand-roll the decomposition — the library buys us nothing there. By its own docstring it's a "partial implementation of ISO8601 durations (no months)."
  • It drops cases we explicitly support. It rejects months, can't represent fractional seconds, and has no negatives. Our converter handles all three. Swapping it in would silently narrow behavior and break frontend round-tripping (dayjs can emit P1M).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants